/* Copyright (C) 2014-2016 Dan Chapman <dpniel@ubuntu.com>

   This file is part of Dekko email client for Ubuntu Devices/

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of
   the License or (at your option) version 3

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "MessageModel.h"
#include <QUrl>
#include <QMimeDatabase>
#include "app/Utils/FindTextPart.h"

namespace Dekko
{
namespace Models
{

MessageModel::MessageModel(QObject *parent)
    : QObject(parent), m_preferPlainText(false), m_from(0), m_to(0), m_cc(0), m_bcc(0), m_attachments(0), m_imapModel(0),
      m_mpnam(0), m_reply(0), m_identities(0)
{
    m_from = new QQmlObjectListModel<MessageContact>(this);
    m_to = new QQmlObjectListModel<MessageContact>(this);
    m_cc = new QQmlObjectListModel<MessageContact>(this);
    m_bcc = new QQmlObjectListModel<MessageContact>(this);
    m_attachments = new QQmlObjectListModel<MessageAttachment>(this);
    m_mpnam = new Imap::Network::MsgPartNetAccessManager(this);
    m_identities = new Accounts::MultipleAccountSenderIdentityModel(this);
}

MessageModel::MessageModel(QObject *parent, Imap::Mailbox::Model *model) :
    QObject(parent), m_preferPlainText(false), m_from(0), m_to(0), m_cc(0), m_bcc(0), m_attachments(0), m_imapModel(model),
    m_mpnam(0), m_reply(0), m_identities(0)
{
    m_from = new QQmlObjectListModel<MessageContact>(this);
    m_to = new QQmlObjectListModel<MessageContact>(this);
    m_cc = new QQmlObjectListModel<MessageContact>(this);
    m_bcc = new QQmlObjectListModel<MessageContact>(this);
    m_attachments = new QQmlObjectListModel<MessageAttachment>(this);
    m_mpnam = new Imap::Network::MsgPartNetAccessManager(this);
    m_identities = new Accounts::MultipleAccountSenderIdentityModel(this);
    initialize();
}

void MessageModel::initialize()
{
    m_subtree = new Imap::Mailbox::SubtreeModelOfModel(this);
    m_subtree->setSourceModel(m_imapModel);
    connect(m_subtree, SIGNAL(modelReset()), this, SIGNAL(envelopeChanged()));
    connect(m_subtree, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SIGNAL(envelopeChanged()));
    connect(this, SIGNAL(envelopeChanged()), this, SIGNAL(flagsChanged()));
    connect(m_imapModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(handleModelDataChanged(QModelIndex,QModelIndex)));

}

QObject *MessageModel::attachments() const
{
    return m_attachments;
}

QString MessageModel::subject() const
{
    return m_message.data(Imap::Mailbox::RoleMessageSubject).toString();
}

QDateTime MessageModel::date() const
{
    return m_message.data(Imap::Mailbox::RoleMessageDate).toDateTime();
}

QObject *MessageModel::from() const
{
    return m_from;
}

QObject *MessageModel::to() const
{
    return m_to;
}

QObject *MessageModel::cc() const
{
    return m_cc;
}

QObject *MessageModel::bcc() const
{
    return m_bcc;
}

QString MessageModel::toRecipientsString()
{

    auto listContainsMe = [](Accounts::MultipleAccountSenderIdentityModel *model, const QStringList &recips) -> int {
        int idx = -1;
        for (int i = 0; i < recips.size(); ++i) {
            QString recip = recips.at(i);
            if (model->addressInIdentities(recip)) {
                return i;
            }
        }
        return idx;
    };

    QStringList addr; // list of addresses to compare
    // create our list of TO recipients addresses
    Q_FOREACH(auto a, m_to->toList()) {
        addr.append(a->email());
    }
    int myIndex = listContainsMe(m_identities, addr);
    // we actually want the name here
    addr.clear();
    Q_FOREACH(auto n, m_to->toList()) {
        addr.append(n->name());
    }
    // Now move "Me" to the start
    if (myIndex != -1) {
        addr.move(myIndex, 0);
        // and rename to "you" so that is is displayed
        // like "to you, Bob, Phil..."
        addr.replace(0, tr("you"));
    }
    return tr("to %1").arg(addr.join(", "));
}

bool MessageModel::hasValidIndex() const
{
    return m_message.isValid();
}

bool MessageModel::isListPost() const
{
    QList<QUrl> listPost;
    Q_FOREACH(const QVariant &item, m_message.data(Imap::Mailbox::RoleMessageHeaderListPost).toList()) {
        listPost << item.toUrl();
    }
    return !listPost.isEmpty();
}

QList<QByteArray> MessageModel::references() const
{
    return m_message.data(Imap::Mailbox::RoleMessageHeaderReferences).value<QList<QByteArray> >();
}

QByteArray MessageModel::messageId() const
{
    return m_message.data(Imap::Mailbox::RoleMessageMessageId).toByteArray();
}

QModelIndex MessageModel::messageFirstChildIndex() const
{
    return m_message.child(0, 0);
}

QModelIndex MessageModel::messageIndex() const
{
    return m_message;
}

void MessageModel::openMessage()
{
    setMessage(m_mailbox, m_uid);
}

void MessageModel::markMessageDeleted()
{
    if (!m_message.isValid()) {
        return;
    }
    Imap::Mailbox::Model *model = const_cast<Imap::Mailbox::Model *>(dynamic_cast<const Imap::Mailbox::Model *>(m_message.model()));
    Q_ASSERT(model);
    if (!model->isNetworkAvailable()) {
        return;
    }
    m_imapModel->markMessagesDeleted(QModelIndexList() << m_message, isMarkedDeleted() ? Imap::Mailbox::FLAG_REMOVE : Imap::Mailbox::FLAG_ADD);
}

void MessageModel::markMessageRead(bool marked)
{
    if (!m_message.isValid())
        return;
    Imap::Mailbox::Model *model = const_cast<Imap::Mailbox::Model *>(dynamic_cast<const Imap::Mailbox::Model *>(m_message.model()));
    Q_ASSERT(model);
    if (!model->isNetworkAvailable())
        return;
    if (!m_message.data(Imap::Mailbox::RoleMessageIsMarkedRead).toBool())
        model->markMessagesRead(QModelIndexList() << m_message, Imap::Mailbox::FLAG_ADD);
}

void MessageModel::markMessageFlagged()
{
    if (!m_message.isValid()) {
        return;
    }
    Imap::Mailbox::Model *model = const_cast<Imap::Mailbox::Model *>(dynamic_cast<const Imap::Mailbox::Model *>(m_message.model()));
    Q_ASSERT(model);
    if (!model->isNetworkAvailable()) {
        return;
    }
    model->setMessageFlags(QModelIndexList() << m_message, QStringLiteral("\\Flagged"), isMarkedFlagged() ? Imap::Mailbox::FLAG_REMOVE : Imap::Mailbox::FLAG_ADD);
}

void MessageModel::setMessageModelIndex(QModelIndex index)
{
    setMessage(index);
}

QModelIndex MessageModel::getModelIndexFromPartId(QString partId)
{
    QModelIndex item = messageFirstChildIndex();
    QStringList separated = partId.split('.');
    for (QStringList::const_iterator it = separated.constBegin(); it != separated.constEnd(); ++it) {
        bool ok;
        uint number = it->toUInt(&ok);
        Q_ASSERT(ok);
        // Normal path: descending down and finding the correct part
        QModelIndex child = item.child(0, 0);
        if (child.isValid() && child.data(Imap::Mailbox::RolePartIsTopLevelMultipart).toBool())
            item = child;
        item = item.child(number - 1, 0);
        Q_ASSERT(item.isValid());
    }
    return item;
}

QList<QByteArray> MessageModel::replyReferences() const
{
    if(!m_message.isValid()) {
        return QList<QByteArray>();
    }
    QList<QByteArray> res = references();
    res.append(messageId());
    return res;
}

QUrl MessageModel::mainPartUrl()
{
    return m_inlineParts.top()->pathToPartUrl();
}

bool MessageModel::mainPartIsHtml() const
{
    return m_inlineParts.top()->isHtml();
}

void MessageModel::setAccountIds(const QStringList &ids)
{
    m_identities->setAccountIds(ids);
}

void MessageModel::setMessage(const QString &mboxName, const uint uid)
{
    if (mboxName.isEmpty())
        return;
    Q_ASSERT(m_imapModel);
    setMessage(m_imapModel->messageIndexByUid(mboxName, uid));
}

void MessageModel::setMessage(const QModelIndex &message)
{
    if (!message.isValid()) {
        return;
    }
    Q_ASSERT(message.isValid() || message.model() == m_imapModel);
    while(!m_inlineParts.isEmpty()) {
        InlinePart *part = m_inlineParts.pop();
        delete part;
    }
    if (!m_attachments->isEmpty()) {
        m_attachments->clear();
        emit attachmentsChanged();
    }
    m_message = message;
    emit messageChanged();
    initContactModels();
    walkMessage(m_message.child(0, 0), 0, false);
    if (!m_inlineParts.isEmpty()) {
        emit mainPartUrlChanged();
        emit attachmentsChanged();
    }
}

void MessageModel::messageClosed()
{
    m_message = QModelIndex();
    emit messageChanged();
}

void MessageModel::fetchPart(const QModelIndex &msgIndex, const QModelIndex &part)
{
    m_fetchPartIndex = part;
    QNetworkRequest request;
    QUrl url;
    url.setScheme(QStringLiteral("dekko-imap"));
    url.setHost(QStringLiteral("msg"));
    url.setPath(part.data(Imap::Mailbox::RolePartPathToPart).toString());
    request.setUrl(url);
    qDebug() << __func__ << "Requesting url: " << url.toString();
    m_mpnam->setModelMessage(msgIndex);
    m_reply = m_mpnam->get(request);
    connect(m_reply, SIGNAL(finished()), this, SLOT(handlePartFetched()));
//    connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(handleRequestError()));
}

void MessageModel::handleModelDataChanged(const QModelIndex &topLeft, const QModelIndex &btmRight)
{
    Q_ASSERT(topLeft.row() == btmRight.row());
    Q_ASSERT(topLeft.parent() == btmRight.parent());
    Q_ASSERT(topLeft.model() == btmRight.model());

    if (m_message.isValid() && topLeft == m_message)
        emit flagsChanged();
}

void MessageModel::handlePartFetched()
{
    if (m_fetchPartIndex.data(Imap::Mailbox::RoleIsFetched).toBool()) {
        emit partNowAvailable(m_fetchPartIndex);
    }
}

void MessageModel::walkMessage(const QModelIndex &partIndex, const int recursionDepth, const bool isPreferredIndex)
{
    Q_ASSERT(partIndex.isValid());
    if (!partIndex.isValid()) {
        return;
    }
    if (recursionDepth > 1000) {
        qWarning() << "We are way too deep in the nesting here.... stopping now to prevent stack exhaustion";
    }
    QString mimeType = partIndex.data(Imap::Mailbox::RolePartMimeType).toString().toLower();
    qDebug() << "Mime type: " << mimeType << "Depth: " << recursionDepth;
    bool isMessageRfc822 = mimeType == QLatin1String("message/rfc822");
    bool isCompoundMimeType = mimeType.startsWith(QLatin1String("multipart/")) || isMessageRfc822;

    // Check whether we can render this MIME type at all
    QStringList allowedMimeTypes;
    allowedMimeTypes << QStringLiteral("text/html") << QStringLiteral("text/plain") << QStringLiteral("image/jpeg") <<
                        QStringLiteral("image/jpg") << QStringLiteral("image/pjpeg") << QStringLiteral("image/png") << QStringLiteral("image/gif");
    bool recognizedMimeType = isCompoundMimeType || allowedMimeTypes.contains(mimeType);
    bool isDerivedMimeType = false;
    if (!recognizedMimeType) {
        // QMimeType's docs say that one shall use inherit() to check for "is this a recognized MIME type".
        // E.g. text/x-csrc inherits text/plain.
        QMimeType partType = QMimeDatabase().mimeTypeForName(mimeType);
        Q_FOREACH(const QString &candidate, allowedMimeTypes) {
            if (partType.isValid() && !partType.isDefault() && partType.inherits(candidate)) {
                // Looks like we shall be able to show this
                recognizedMimeType = true;
                // If it's a derived MIME type, it makes sense to not block inline display, yet still make it possible to hide it
                // using the regular attachment controls
                isDerivedMimeType = true;
                break;
            }
        }
    }
    // Check whether it shall be wrapped inside an AttachmentView
    // From section 2.8 of RFC 2183: "Unrecognized disposition types should be treated as `attachment'."
    const QByteArray contentDisposition = partIndex.data(Imap::Mailbox::RolePartBodyDisposition).toByteArray().toLower();
    const bool isInline = contentDisposition.isEmpty() || contentDisposition == "inline";
    const bool looksLikeAttachment = !partIndex.data(Imap::Mailbox::RolePartFileName).toString().isEmpty();
    const bool wrapInAttachmentView = (looksLikeAttachment || !isInline || !recognizedMimeType || isDerivedMimeType || isMessageRfc822);
    if(wrapInAttachmentView) {
        if (!isCompoundMimeType) {
            partIndex.data(Imap::Mailbox::RolePartForceFetchFromCache);
        }
        MessageAttachment *attachment = new MessageAttachment;
        attachment->setPartIndex(partIndex);
        m_attachments->append(attachment);
        qDebug() << "APPENDING ATTACHMENT: " << attachment->fileName();
        qDebug() << "MODEL COUNT: " << m_attachments->count();
        return;
    }
    if (mimeType.startsWith(QLatin1String("multipart/"))) {
        // it's a compound part
        if (mimeType == QLatin1String("multipart/alternative")) {
            return walkMultipartAlternative(partIndex, recursionDepth);
        } else if (mimeType == QLatin1String("multipart/signed")) {
            return walkMultipartSigned(partIndex, recursionDepth); // TODO: get multipart/signed working
        } else if (mimeType == QLatin1String("multipart/related")) {
            // We need to find the mainpart
            QModelIndex mainPartIndex;
            QVariant mainPartCID = partIndex.data(Imap::Mailbox::RolePartMultipartRelatedMainCid);
            if (mainPartCID.isValid()) {
                mainPartIndex = Imap::Network::MsgPartNetAccessManager::cidToPart(partIndex, mainPartCID.toByteArray());
            }
            if (!mainPartIndex.isValid()) {
                // The Content-Type-based start parameter was not terribly useful. Let's find the HTML part manually.
                QModelIndex candidate = partIndex.child(0, 0);
                while (candidate.isValid()) {
                    if (candidate.data(Imap::Mailbox::RolePartMimeType).toString() == QLatin1String("text/html")) {
                        mainPartIndex = candidate;
                        break;
                    }
                    candidate = candidate.sibling(candidate.row() + 1, 0);
                }
            }

            if (mainPartIndex.isValid()) {
                if (mainPartIndex.data(Imap::Mailbox::RolePartMimeType).toString() == QLatin1String("text/html")) {
                    return walkMessage(mainPartIndex, recursionDepth+1, true);
                } else {
                    // Sorry, but anything else than text/html is by definition suspicious here. Better than picking some random
                    // choice, let's just show everything.
                    return walkGenericMultiPart(partIndex, recursionDepth);
                }
            } else {
                // The RFC2387's wording is clear that in absence of an explicit START argument, the first part is the starting one.
                // On the other hand, I've seen real-world messages whose first part is some utter garbage (an image sent as
                // application/octet-stream, for example) and some *other* part is an HTML text. In that case (and if we somehow
                // failed to pick the HTML part by a heuristic), it's better to show everything.
                return walkGenericMultiPart(partIndex, recursionDepth);
            }
        } else {
            return walkGenericMultiPart(partIndex, recursionDepth);
        }
    } else if (mimeType == QLatin1String("message/rfc822")) {
        return walkGenericMultiPart(partIndex, recursionDepth);
    } else {
        partIndex.data(Imap::Mailbox::RolePartForceFetchFromCache);
        const bool isPlainText = mimeType == QLatin1String("text/plain");
        const bool isHtml = mimeType == QLatin1String("text/html");
        if (isPlainText) {
            if (m_inlineParts.isEmpty() && (isPreferredIndex || !recursionDepth)) {
                // This is a temporary fix. We still need to parse this message with the plaintextformatter
                qDebug() << "Settings plaintextpart";

                InlinePart *newPart = new InlinePart(this, partIndex.data(Imap::Mailbox::RolePartPathToPart).toString(), m_accountId, m_mailbox, QString::number(uid()), InlinePart::PLAIN);
                m_inlineParts.push(newPart);
                return;
            }
        } else if (isHtml) {
            if (isPreferredIndex || !recursionDepth) {
                InlinePart *newHtmlPart = new InlinePart(this, partIndex.data(Imap::Mailbox::RolePartPathToPart).toString(), m_accountId, m_mailbox, QString::number(uid()), InlinePart::HTML);
                m_inlineParts.push(newHtmlPart);
            }
        }
    }
}

void MessageModel::walkMultipartAlternative(const QModelIndex &partIndex, const int recursionDepth)
{
    int interestedIndex = -1;
    for (int i = 0; i < partIndex.model()->rowCount(partIndex); ++i) {
        QModelIndex nextPart = partIndex.child(i, 0);
        Q_ASSERT(nextPart.isValid());
        QString mimeType = nextPart.data(Imap::Mailbox::RolePartMimeType).toString();
        const bool isPlainText = mimeType == QLatin1String("text/plain");
        const bool isHtml = mimeType == QLatin1String("text/html");
        if (isPlainText && m_preferPlainText) {
            interestedIndex = i;
        } else if (isHtml && !m_preferPlainText) {
            interestedIndex = i;
        }
    }
    // Right so if our interested index is still at -1 then we set our preferred part to the last
    // index in our list, as later parts should always win.
    int preferredPart = interestedIndex == -1 ? partIndex.model()->rowCount(partIndex) - 1 : interestedIndex;

    // Now the second loop to walk the parts and create the actual markup for our preferredPart and others if showHidden is true
    for (int i = 0; i < partIndex.model()->rowCount(partIndex); ++i) {
        QModelIndex nextPart = partIndex.child(i, 0);
        Q_ASSERT(nextPart.isValid());
        walkMessage(nextPart, recursionDepth + 1, i == preferredPart);
    }
}

void MessageModel::walkMultipartSigned(const QModelIndex &partIndex, const int recursionDepth)
{
    Q_ASSERT(partIndex.isValid());
//    emit error(QString("Multipart/signed un-supported at this time"));
    // lets walk it and see if we find any displayble though
    uint childrenCount = partIndex.model()->rowCount(partIndex);
    qDebug() << "children count is" << childrenCount;
    if (childrenCount == 1) {
        qDebug() << "Children count is 1";
        QModelIndex anotherPart = partIndex.child(0, 0);
        Q_ASSERT(anotherPart.isValid()); // guaranteed by the MVC
        walkMessage(anotherPart, recursionDepth + 1, true);
    } else if (childrenCount != 2) {
        qWarning() << QStringLiteral("Malformed multipart/signed message: %1 nested parts").arg(QString::number(childrenCount));
//        emit error(QString("Malformed multipart/signed message: %1 nested parts").arg(QString::number(childrenCount)));
        return;
    } else {
        Q_ASSERT(childrenCount == 2); // from the if logic; FIXME: refactor
        QModelIndex anotherPart = partIndex.child(0, 0);
        Q_ASSERT(anotherPart.isValid()); // guaranteed by the MVC
        walkMessage(anotherPart, recursionDepth + 1, true);
    }
    return;
}

void MessageModel::walkGenericMultiPart(const QModelIndex &partIndex, const int recursionDepth)
{
    bool plainTextSeen = false;
    bool htmlSeen = false;
    QModelIndex plainTextSeenIndex;
    QModelIndex htmlSeenIndex;
    int rd = 0;
    for (int i = 0; i < partIndex.model()->rowCount(partIndex); ++i) {
        QModelIndex nextPart = partIndex.child(i, 0);
        QString mimeType = nextPart.data(Imap::Mailbox::RolePartMimeType).toString();
        qDebug() << "MIMETYPE:" << mimeType;

        bool isAttachment = !nextPart.data(Imap::Mailbox::RolePartFileName).toString().isEmpty();
        if (mimeType == QLatin1String("text/plain") && !isAttachment) {
            plainTextSeen = true;
            if (!plainTextSeenIndex.isValid()) {
                plainTextSeenIndex = nextPart;
            }
            rd = recursionDepth + 1;
        } else if (mimeType == QLatin1String("text/html") && !htmlSeen && !isAttachment) {
            htmlSeen = true;
            htmlSeenIndex = nextPart;
            rd = recursionDepth + 1;
        } else {
            Q_ASSERT(nextPart.isValid());
            walkMessage(nextPart, recursionDepth + 1, true);
        }
    }
    if (htmlSeen  && plainTextSeen) {
        if (m_preferPlainText) {
            walkMessage(plainTextSeenIndex, rd, true);
        } else {
            walkMessage(htmlSeenIndex, rd, true);
        }
    } else if (htmlSeen) {
        walkMessage(htmlSeenIndex, rd, true);
    } else if (plainTextSeen) {
        walkMessage(plainTextSeenIndex, rd, true);
    } else {
        qWarning() << "No usable message part";
    }
}

void MessageModel::initContactModels()
{
    enum contactType {from, to, cc, bcc};
    auto createModel = [&](const contactType &type, QAbstractItemModel *model) {
        QQmlObjectListModel<MessageContact> *objModel = static_cast<QQmlObjectListModel<MessageContact> *>(model);
        if (!objModel->isEmpty()) {
            objModel->clear();
        }
        Q_ASSERT(objModel->isEmpty());
        QVariantList recipientList;
        switch (type) {
        case contactType::from:
            recipientList = m_message.data(Imap::Mailbox::RoleMessageFrom).toList();
            break;
        case contactType::to:
            recipientList = m_message.data(Imap::Mailbox::RoleMessageTo).toList();
            break;
        case contactType::cc:
            recipientList = m_message.data(Imap::Mailbox::RoleMessageCc).toList();
            break;
        case contactType::bcc:
            recipientList = m_message.data(Imap::Mailbox::RoleMessageBcc).toList();
            break;
        default:
            Q_ASSERT(false);
        }
        Q_FOREACH (const QVariant &list, recipientList) {
            QVariantList array = list.toList();
            MessageContact *contact = new MessageContact(0, Imap::Message::MailAddress(array[0].toString(), array[1].toString(), array[2].toString(), array[3].toString()));
            objModel->append(contact);
        }
    };
    createModel(from, m_from);
    createModel(to, m_to);
    createModel(cc, m_cc);
    createModel(bcc, m_bcc);
    emit envelopeChanged();
}

QObject *MessageModel::imapModel()
{
    return m_imapModel;
}

void MessageModel::setImapModel(QObject *imapModel)
{
    if (!imapModel) {
        return;
    }
    m_imapModel = static_cast<Imap::Mailbox::Model *>(imapModel);
    initialize();
}

void MessageModel::setModel(Imap::Mailbox::Model *model)
{
    m_imapModel = model;
    initialize();
}

uint MessageModel::uid()
{
    return m_message.data(Imap::Mailbox::RoleMessageUid).toUInt();
}

QString MessageModel::mailbox()
{
    return m_mailbox;
}

void MessageModel::setMailbox(QString mailbox)
{
    m_mailbox = mailbox;
    emit mailboxChanged();
}

QModelIndex MessageModel::mainTextPartIndex()
{
    QModelIndex idx;
    QString partMessage;
    Dekko::Utils::FindTextPart::findTextPartOfMessage(messageIndex(), idx, partMessage, 0);
    return idx;
}

bool MessageModel::isMarkedDeleted() const
{
    return m_message.data(Imap::Mailbox::RoleMessageIsMarkedDeleted).toBool();
}

bool MessageModel::isMarkedRead() const
{
    return m_message.data(Imap::Mailbox::RoleMessageIsMarkedRead).toBool();
}

bool MessageModel::isMarkedForwarded() const
{
    return m_message.data(Imap::Mailbox::RoleMessageIsMarkedForwarded).toBool();
}

bool MessageModel::isMarkedReplied() const
{
    return m_message.data(Imap::Mailbox::RoleMessageIsMarkedReplied).toBool();
}

bool MessageModel::isMarkedRecent() const
{
    return m_message.data(Imap::Mailbox::RoleMessageIsMarkedRecent).toBool();
}

bool MessageModel::isMarkedFlagged() const
{
    return m_message.data(Imap::Mailbox::RoleMessageIsMarkedFlagged).toBool();
}

QString MessageAttachment::partId() const
{
    return m_partIndex.data(Imap::Mailbox::RolePartId).toString();
}

QString MessageAttachment::mimeType() const
{
    return m_partIndex.data(Imap::Mailbox::RolePartMimeType).toString();
}

QString MessageAttachment::iconName() const
{
    return QStringLiteral("%1-symbolic").arg(QMimeDatabase().mimeTypeForName(mimeType()).genericIconName());
}

QString MessageAttachment::fileName() const
{
    return m_partIndex.data(Imap::Mailbox::RolePartFileName).toString();;
}

QString MessageAttachment::size() const
{
    return UiUtils::Formatting::prettySize(m_partIndex.data(Imap::Mailbox::RolePartOctets).toUInt(),
                                           UiUtils::Formatting::BytesSuffix::WITH_BYTES_SUFFIX);;
}

QModelIndex MessageAttachment::partIndex() const
{
    return m_partIndex;
}

void MessageAttachment::setPartIndex(const QModelIndex &index) {
    m_partIndex = index;
    if (m_downloader != NULL) {
        delete m_downloader;
        m_downloader = 0;
    }
    m_downloader = new Dekko::Utils::AttachmentDownloader(this, m_partIndex);
    connect(m_downloader, &Dekko::Utils::AttachmentDownloader::downloadProgress, this, &MessageAttachment::handleDownloadProgressChanged);
    connect(m_downloader, &Dekko::Utils::AttachmentDownloader::error, [&](const QString &errorString) {
        m_fetchInProgress = false;
        emit fetchInProgressChanged(m_fetchInProgress);
        m_errorString = errorString;
        emit this->error(m_errorString);
    });
    connect(m_downloader, &Dekko::Utils::AttachmentDownloader::downloadComplete, [&](const QString &url) {
        m_fetchInProgress = false;
        emit fetchInProgressChanged(m_fetchInProgress);
        m_partUrl = url;
        emit this->partUrlChanged(m_partUrl);
    });
    emit partIndexChanged();
}

uint MessageAttachment::downloadProgress() const
{
    return m_downloadProgress;
}

void MessageAttachment::fetch()
{
    if (m_downloader != NULL) {
        m_downloader->fetchPart();
        m_fetchInProgress = true;
        emit fetchInProgressChanged(m_fetchInProgress);
    }
}

void MessageAttachment::cleanup() const
{
    if (m_downloader != NULL) {
        m_downloader->cleanTmpFile();
    }
}

QString MessageAttachment::errorString() const
{
    return m_errorString;
}

QString MessageAttachment::partUrl() const
{
    return m_partUrl;
}

bool MessageAttachment::fetchInProgress() const
{
    return m_fetchInProgress;
}

void MessageAttachment::handleDownloadProgressChanged(qint64 downloaded, qint64 total)
{
    int percent = ((int)(downloaded / total));
    qDebug() << "Downloaded: " << downloaded << " Total: " << total << "Percentage: " << percent;
    m_downloadProgress = percent;
    emit downloadProgressChanged(m_downloadProgress);
}

InlinePart::InlinePart(QObject *parent, const QString &path, const QString &accId, const QString &mbox, const QString &id, const InlinePart::CType type) :
QObject(parent)
{
    contentType = type;
    pathToPart = path;
    accountId = accId;
    uid = id;
    mailbox = mbox;
}

bool InlinePart::isPlaintText() {
    return contentType == CType::PLAIN;
}

bool InlinePart::isHtml() {
    return contentType == CType::HTML;
}

QUrl InlinePart::pathToPartUrl() {
    QUrl url;
    url.setScheme(QStringLiteral("dekko-imap"));
    url.setHost(QStringLiteral("msg"));
    url.setPath(pathToPart);
    QUrlQuery query;
    if (isPlaintText())
        query.addQueryItem(QStringLiteral("requestFormatting"), QStringLiteral("true"));
    query.addQueryItem(QStringLiteral("accountid"), accountId);
    query.addQueryItem(QStringLiteral("mailbox"), mailbox);
    query.addQueryItem(QStringLiteral("uid"), uid);
    url.setQuery(query);
    return url;
}

MessageContact::MessageContact(QObject *parent, const Imap::Message::MailAddress &mailAddress) : QObject(parent),
    m_mailAddress(mailAddress)
{
}

QString MessageContact::initials()
{
    QString name = this->name();
    if (name.isEmpty() || !name.at(0).isLetter()) {
        return QString();
    }
    // Intitials string
    QString initials;
    // Now break up the name, we have to set the encoding here as QT_NO_CAST_FROM/TO_ASCII is set
    QStringList parts = name.split(QStringLiteral(" "));
    if (parts.first().at(0).isLetter()) {
        initials += parts.first().at(0).toUpper();
    }
    if (parts.size() > 1) {
        if (parts.last().at(0).isLetter()) {
            initials += parts.last().at(0).toUpper();
        }
    }
    return initials;
}

QString MessageContact::name() const
{
    return m_mailAddress.prettyName(Imap::Message::MailAddress::FORMAT_JUST_NAME);
}

QString MessageContact::displayName() const
{
    return m_mailAddress.prettyName(Imap::Message::MailAddress::FORMAT_READABLE);
}

QString MessageContact::email() const
{
    return m_mailAddress.prettyName(Imap::Message::MailAddress::FORMAT_JUST_EMAIL);
}

}
}
